﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;

namespace NovoDataBase
{
    public class DataProcess
    {       
        public DataProcess() { }

        public static void RemoveEmptyZone(DataSet _ds)
        {
            string tempData;
            int ix_c = 0;
            int ix_r = 0;
            DataTable dt;
            for (int ix_t = 0; ix_t < _ds.Tables.Count; ix_t++)
            {
                 dt = _ds.Tables[ix_t];
                // Removes empty columns 
                for (ix_c = dt.Columns.Count - 1; ix_c > 0 ; ix_c--)
                {
                    for (ix_r = 0; ix_r < dt.Rows.Count; ix_r++)
                    {
                        tempData = dt.Rows[ix_r][ix_c].ToString();
                        if (dt.Rows[ix_r][ix_c].ToString() != "")
                            break;
                    }

                    // if all cells are empty, then remove current column
                    if(ix_r == dt.Rows.Count)
                        dt.Columns.RemoveAt(ix_c);
                }

                // Remove empty rows
                for (ix_r = dt.Rows.Count - 1; ix_r > 0; ix_r--)
                {
                    for (ix_c = 0; ix_c < dt.Columns.Count; ix_c++)
                    {
                        tempData = dt.Rows[ix_r][ix_c].ToString();
                        if (dt.Rows[ix_r][ix_c].ToString() != "")
                            break;
                    }

                    // if all cells are empty, then remove current row
                    if(ix_c == dt.Columns.Count)
                        dt.Rows.RemoveAt(ix_r);
                }
            }

            return;
        }

        public static void RemoveEmptyColumn(DataTable _dt)
        {
            //string tempData;
            int ix_c = 0;
            int ix_r = 0;
            // Removes empty columns 
            for (ix_c = _dt.Columns.Count - 1; ix_c > 0; ix_c--)
            {
                for (ix_r = 0; ix_r < _dt.Rows.Count; ix_r++)
                {
                    //tempData = _dt.Rows[ix_r][ix_c].ToString();
                    if (_dt.Rows[ix_r][ix_c].ToString() != "")
                        break;
                }

                // if all cells are empty, then remove current column
                if (ix_r == _dt.Rows.Count)
                    _dt.Columns.RemoveAt(ix_c);
            }

            return;
        }

        public static void RemoveEmptyRow(DataTable _dt)
        {
            //string tempData;
            int ix_c = 0;
            int ix_r = 0;
            // Remove empty rows
            for (ix_r = _dt.Rows.Count - 1; ix_r > 0; ix_r--)
            {
                for (ix_c = 0; ix_c < _dt.Columns.Count; ix_c++)
                {
                    //tempData = dt.Rows[ix_r][ix_c].ToString();
                    if (_dt.Rows[ix_r][ix_c].ToString() != "")
                        break;
                }

                // if all cells are empty, then remove current row
                if (ix_c == _dt.Columns.Count)
                    _dt.Rows.RemoveAt(ix_r);
            }

            return;
        }

        /// <summary>
        /// Remove empty cells. First removes columns which without header(based on 1st row info). Then remove all empty rows.
        /// </summary>
        /// <param name="_dt">The datatable for cleaning</param>
        public static void CleanRegTable(DataTable _dt)
        {            
            //string tempData;
            int ix_c = 0;
            int ix_r = 0;
            // Removes empty columns 
            for (ix_c = _dt.Columns.Count - 1; ix_c > 0; ix_c--)
            {
                //tempData = _dt.Rows[0][ix_c].ToString();
                if (_dt.Rows[0][ix_c].ToString() == "")
                    _dt.Columns.RemoveAt(ix_c);
            }

            // Remove empty rows
            for (ix_r = _dt.Rows.Count - 1; ix_r > 0; ix_r--)
            {
                for (ix_c = 0; ix_c < _dt.Columns.Count; ix_c++)
                {
                    //tempData = dt.Rows[ix_r][ix_c].ToString();
                    if (_dt.Rows[ix_r][ix_c].ToString() != "")
                        break;
                }

                // if all cells are empty, then remove current row
                if (ix_c == _dt.Columns.Count)
                    _dt.Rows.RemoveAt(ix_r);
            }

            return;
        }

    }

    public class ExcelToRegMap
    {
        static bool logEn = true;
        static string logFileName = Environment.CurrentDirectory + "excel_import_log.txt";
        public ExcelToRegMap() { }

        private static void ClearLog()
        {
            if (!logEn)
                return;

            StreamWriter sw = new StreamWriter(logFileName, false);
            sw.WriteLine("Log at " + DateTime.Now.ToString());
            sw.Close();
        }
        private static void PrintInfo(string msg)
        {
            if (!logEn)
                return;

            StreamWriter sw = new StreamWriter(logFileName, true);
            sw.WriteLine(msg);
            sw.Close();
        }
        private static void PrintError(string msg)
        {
            if (!logEn)
                return;

            StreamWriter sw = new StreamWriter(logFileName, true);
            sw.WriteLine("\r\nERR: " + msg);
            sw.Close();
        }

        private static bool CheckGroupHeader(DataRow _dr)
        {
            string key_startAddr = "start addr";
            string key_endAddr = "end addr";
            string key_blockName = "block name";
            string key_size = "size";
            string key_description = "description";
            string key_comment = "comment";
            bool ret = false;

            //Console.WriteLine(_dr[0].ToString().Trim().ToUpper());
            //Console.WriteLine(key_startAddr.Trim().ToUpper());

            //Console.WriteLine(_dr[1].ToString().Trim().ToUpper());
            //Console.WriteLine(key_endAddr.Trim().ToUpper());

            //Console.WriteLine(_dr[2].ToString().Trim().ToUpper());
            //Console.WriteLine(key_blockName.Trim().ToUpper());

            //Console.WriteLine(_dr[3].ToString().Trim().ToUpper());
            //Console.WriteLine(key_size.Trim().ToUpper());

            //temp_ret = _dr[0].ToString().Trim().ToUpper().Contains(key_startAddr.Trim().ToUpper());
            //temp_ret = _dr[1].ToString().Trim().ToUpper().Contains(key_endAddr.Trim().ToUpper());
            //temp_ret = _dr[2].ToString().Trim().ToUpper().Contains(key_blockName.Trim().ToUpper());
            //temp_ret = _dr[3].ToString().Trim().ToUpper().Contains(key_size.Trim().ToUpper());

            // Just judge first important items
            if (_dr[0].ToString().Trim().ToUpper().Contains(key_startAddr.Trim().ToUpper()) &&
                _dr[1].ToString().Trim().ToUpper().Contains(key_endAddr.Trim().ToUpper()) &&
                _dr[2].ToString().Trim().ToUpper().Contains(key_blockName.Trim().ToUpper()) &&
                _dr[3].ToString().Trim().ToUpper().Contains(key_size.Trim().ToUpper()))
            {
                ret = true;
            }


            return ret;
        }
        private static RegGroup DecodeRegGroup(DataRow _dr) 
        {
            UInt32 _startAddr = 0;
            UInt32 _endAddr = 0;
            string _name = string.Empty;
            UInt32 _size =  0;
            string _description = string.Empty;
            string _comment = string.Empty;

            bool ret = false;
            // StartAddr
            string tempStr = _dr[(int)itemIx_RegGroup.startAddr].ToString().ToUpper().Replace("0X", "0").Trim();
            if (tempStr == "")
                return null;
            ret = UInt32.TryParse(tempStr, NumberStyles.HexNumber, null, out _startAddr );
            if (ret == false)
                return null;
            
            // EndAddr
            tempStr = _dr[(int)itemIx_RegGroup.endAddr].ToString().ToUpper().Replace("0X", "0").Trim();
            if (tempStr == "")
                return null;
            ret = UInt32.TryParse(tempStr, NumberStyles.HexNumber, null, out _endAddr);
            if (ret == false)
                return null;

            // Name
            _name = _dr[(int)itemIx_RegGroup.name].ToString();//.ToUpper().Trim();
            if (_name == "")
                return null;

            // Size
            UInt32 size_calc = _endAddr - _startAddr + 1;
            tempStr = _dr[(int)itemIx_RegGroup.size].ToString().ToUpper();
            if(tempStr.Contains("K"))   // e.g. 4K or 16K
            {
                ret = UInt32.TryParse(tempStr.Trim('K'), out _size);
                if (ret == false) return null;
                else
                    _size *= 1024;
            }
            else if(tempStr.Contains("0X")) // use Hex formate, like 0x1000
            {
                ret = UInt32.TryParse(tempStr.Replace("0X", "0"), NumberStyles.HexNumber, null, out _size);
                if (ret == false) return null;
            }
            else
            {
                ret = UInt32.TryParse(tempStr, out _size);
                if (ret == false) return null; 
            }

            if(size_calc != _size)
                return null;

            // Description
            _description = _dr[(int)itemIx_RegGroup.description].ToString();

            // Comment
            _comment = _dr[(int)itemIx_RegGroup.comment].ToString();

            RegGroup group = new RegGroup(_startAddr, _endAddr, _name, _size, _description, _comment);
            return group;
        }
        private static List<RegGroup> InitRegGroup(DataTable _dt) 
        {
            List<RegGroup> list_tempRGS = new List<RegGroup> { };            
            // Check header
            DataRow header = _dt.Rows[0];
            RegGroup tempRG = null;
            if (!CheckGroupHeader(header))
                return null;

            // Create Register Group List
            for(int ix_r = 1; ix_r < _dt.Rows.Count; ix_r++) 
            {
                tempRG = DecodeRegGroup(_dt.Rows[ix_r]);
                if (tempRG != null)
                {
                    list_tempRGS.Add(tempRG);
                    PrintInfo(String.Format("Recognized Group {0}:{1}", ix_r, tempRG.Name));
                }
                else
                {
                    PrintError(String.Format("Decode Line {0} failed ", ix_r));
                }
            }

            if(list_tempRGS.Count > 0) 
                return list_tempRGS;
            else return null;
        }

        private static bool CheckRegHeader(DataRow _dr) 
        {
            string key_regName = "Register Name";
            string key_regDescription = "Register Description";
            string key_regOffset = "Register Offset";
            string key_bitField = "Bit Field";
            string key_bfName = "BF Name";
            string key_defaultValue = "Default Value";
            string key_access = "Access";
            string key_bfDescription = "Bit Description";
            string key_permission = "Permission";
            bool ret = false;

            // Just judge important items
            if (_dr[(int)itemIx_Reg.regName].ToString().Trim().ToUpper().Contains(key_regName.Trim().ToUpper()) &&
                _dr[(int)itemIx_Reg.regDes].ToString().Trim().ToUpper().Contains(key_regDescription.Trim().ToUpper()) &&
                _dr[(int)itemIx_Reg.regOffset].ToString().Trim().ToUpper().Contains(key_regOffset.Trim().ToUpper()) &&
                _dr[(int)itemIx_Reg.bitFiled].ToString().Trim().ToUpper().Contains(key_bitField.Trim().ToUpper()) && 
                _dr[(int)itemIx_Reg.bfName].ToString().Trim().ToUpper().Contains(key_bfName.Trim().ToUpper()) &&
                _dr[(int)itemIx_Reg.defaultValue].ToString().Trim().ToUpper().Contains(key_defaultValue.Trim().ToUpper()) &&
                _dr[(int)itemIx_Reg.access].ToString().Trim().ToUpper().Contains(key_access.Trim().ToUpper()) &&
                _dr[(int)itemIx_Reg.bfDes].ToString().Trim().ToUpper().Contains(key_bfDescription.Trim().ToUpper()) && 
                _dr[(int)itemIx_Reg.permission].ToString().Trim().ToUpper().Contains(key_permission.Trim().ToUpper()) )
            {
                ret = true;
            }

            return ret;
        }

        private static bool RegisterRowDecode(DataRow _dr, out RegRow _regRow)
        {
            string tempStr = string.Empty;
            UInt32 tempU32 = 0;
            bool temp_ret = false;
            bool ret = true;
            RegRow newReg = new RegRow();
            _regRow = newReg;
            _regRow.regName = _dr[(int)itemIx_Reg.regName].ToString().Trim();
            _regRow.regDescription = _dr[(int)itemIx_Reg.regDes].ToString();

            // RegOffset - just used for register define row, other bf rows mean nothing
            tempStr = _dr[(int)itemIx_Reg.regOffset].ToString().ToUpper().Replace("0X", "0").Trim();
            if (tempStr == "")  // This row is bit field definition
            {
                _regRow.regOffset = 0;
            }
            else 
            {
                temp_ret = UInt32.TryParse(tempStr, NumberStyles.HexNumber, null, out tempU32);
                ret &= temp_ret;
                if (temp_ret == true)
                    _regRow.regOffset = tempU32;
            }

            #region BitField
            tempStr = _dr[(int)(itemIx_Reg.bitFiled)].ToString().Trim();
            // if no bits info inputed
            tempStr = tempStr.Replace("\n", ""); //tempStr.Replace("[","").Replace("]","");
            if (tempStr == null || tempStr.Length == 0)
            {
                PrintError("No BitField definition found!");
                ret = false;
            }

            // Get byte count of this bf
            string[] bitsIxList = tempStr.Trim().Replace("\n", "").Replace(" ", "").Replace("\r\n", ",").Split(':');
            if (bitsIxList == null || bitsIxList.Length == 0 || bitsIxList.Length > 2)
            {
                PrintError("Wrong BitField definition!");
                ret = false;
            }

            // Just 1 bit, which startBit == endBit
            if (bitsIxList.Length == 1)
            {
                temp_ret = UInt32.TryParse(bitsIxList[0], out tempU32);
                if(temp_ret == false) return false;  
                _regRow.bfStartBit = tempU32;
                _regRow.bfEndBit = tempU32;  
            }
            // Bit range, e.g. 31:4 -> startBit is 4 and endBit is 31
            else
            {
                temp_ret = UInt32.TryParse(bitsIxList[0], out tempU32);
                if (temp_ret == false) return false;
                _regRow.bfEndBit = tempU32;

                temp_ret = UInt32.TryParse(bitsIxList[1], out tempU32);
                if (temp_ret == false) return false;
                _regRow.bfStartBit = tempU32;
            }
            #endregion 

            _regRow.bfName = _dr[(int)itemIx_Reg.bfName].ToString().Trim();
            
            // Default Reg value
            tempStr = _dr[(int)itemIx_Reg.defaultValue].ToString().ToUpper().Replace("0X", "0").Trim();
            if (tempStr == "")
            {
                PrintError("No Default value.");
                ret = false;
            }

            temp_ret = UInt32.TryParse(tempStr, NumberStyles.HexNumber, null, out tempU32);
            if (temp_ret == false)
            {
                PrintError("Wrong Default value.");
                ret = false;
            }
            else
                _regRow.defaultValue = tempU32;

            // Access
            tempStr = _dr[(int)itemIx_Reg.access].ToString().Trim().ToUpper();
            if (tempStr.Contains("R/W") || tempStr.Contains("RW"))
                _regRow.access = RWProperty.RW;
            else if (tempStr.Contains("RO"))
                _regRow.access = RWProperty.RO;
            else if (tempStr.Contains("WO"))
                _regRow.access = RWProperty.WO;
            else
            {
                PrintError("Wrong Access or Access missed .");
                ret = false;
            }

            _regRow.bfDescription = _dr[(int)itemIx_Reg.bfDes].ToString();

            // Permission
            tempStr = _dr[(int)itemIx_Reg.permission].ToString().Trim().ToUpper();
            if (tempStr.Contains("PUBLIC"))
                _regRow.attribute = Permission.Public;
            else if (tempStr.Contains("PRIVATE"))
                _regRow.attribute = Permission.Private;
            else
            {
                PrintError("Wrong Permission or Permission missed .");
                _regRow.attribute = Permission.Public;
            }

            return ret;
        }

        private static Register DecodeRegister(RegGroup _rg, DataRow[] _drs)
        {
            Register tempReg = null;
            RegRow tempRow = new RegRow();

            // 1st row is used for register creation
            if (!RegisterRowDecode(_drs[0], out tempRow))
            {
                PrintError("Register Decode failed.");
                return null;
            }
            else
            {
                tempReg = new Register(_rg, tempRow);
            }

            // Init bitfield with all rows 
            for(int ix = 0; ix < _drs.Length; ix++)
            {
                if (RegisterRowDecode(_drs[ix], out tempRow) == false)
                {
                    PrintError(String.Format("Bit field Decode failed at BF row {0}.", ix));
                    return null;
                }

                //BitField tempBF = new BitField(tempReg, tempRow);
                //tempReg.AddBitField(tempBF);
                tempReg.AddBitField(tempRow);
            }

            return tempReg; 
        }

        private static void DT2RegList(RegGroup _rg, DataTable _dt)
        {
            PrintInfo("Start to Check the header of " + _rg.Name);
            if (CheckRegHeader(_dt.Rows[0]) == false)
            {
                PrintError(string.Format("Group {0} heaer decode failed ", _rg.Name));
                return;
            }

            List<DataRow> list_tempDR = new List<DataRow>();

            // index start from 1 to skip the header row
            for(int ix_r = 1; ix_r < _dt.Rows.Count; ix_r++)
            {
                if (_dt.Rows[ix_r][(int)itemIx_Reg.regName].ToString().Trim() != "")    // start a new register
                {
                    if (list_tempDR.Count > 0)
                    {
                        PrintInfo(String.Format("Row decode for row:{0} in group {1}", (ix_r - list_tempDR.Count + 1).ToString(), _rg.Name));
                        Register tempReg = DecodeRegister(_rg, list_tempDR.ToArray());
                        if (tempReg != null)
                        {
                            _rg.Add(tempReg);
                            PrintInfo("Added new register " + tempReg.RegName);
                        }
                        else
                        {
                            PrintError(String.Format("Register decode failed at row:{0} in group {1} ", 
                                (ix_r - list_tempDR.Count + 1).ToString(), _rg.Name));
                        }
                    }
                    // Clear the list and a new start for next register decoding
                    list_tempDR.Clear();    
                }
                list_tempDR.Add(_dt.Rows[ix_r]);
            }
            // Last Register
            if (list_tempDR.Count > 0)
            {
                Register tempReg = DecodeRegister(_rg, list_tempDR.ToArray());
                if (tempReg != null)
                {
                    _rg.Add(tempReg);
                    PrintInfo("Added new register " + tempReg.RegName);
                }
                else
                {
                    PrintError("The last Register decode failed.");
                }
            }
        }

        public static DeviceInfo DecodeDeviceInfo(DataTable _dt)
        {
            return null;
        }

        public static DataBase_Ver DecodeDBVer(DataTable _dt) 
        {
            return null; 
        }

        public static RegisterMap CreateRegMap(DataSet _dsExcel)
        {
            string enter_regmap = "RegMap$";
            RegisterMap tempRegMap = new RegisterMap();

            ClearLog();
            PrintInfo("Total sheets: " + _dsExcel.Tables.Count.ToString());
            PrintInfo("Decode RegMap");

            List<RegGroup> myRegGroup = InitRegGroup(_dsExcel.Tables[enter_regmap]);
            if (myRegGroup == null)
            {
                PrintError("Failed to decode Register group in RegMap tab!");

                return null;
            }
            else
                PrintInfo(string.Format("Decode {0} Register Group", myRegGroup.Count));


            DataTable tempDT;
            for(int ix = 0; ix < myRegGroup.Count; ix++) 
            {
                tempDT = _dsExcel.Tables[myRegGroup[ix].Name+"$"];
                if (tempDT == null)
                {
                    PrintError(string.Format("Can not find sheet: {0}", myRegGroup[ix].Name));
                    continue;
                }
                PrintInfo("\r\n ------------------------------------------- ");
                PrintInfo(string.Format("Decode {0} ", myRegGroup[ix].Name));
                DT2RegList(myRegGroup[ix], tempDT);
                tempRegMap.AddGroup(myRegGroup[ix]);
            }

            return tempRegMap;
        }
    }

}
